package blethrd

import (
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"unsafe"
)

/*
#cgo windows LDFLAGS: libbletest.dll  BluetoothApis.dll

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

// list devices
int  test_devs_init( intptr_t * pret );
int  test_devs_next( intptr_t ctx, char ** ppath );
void  test_devs_fini( intptr_t ctx );

// open device
int64_t  test_open_device( char * fname );
void  test_close_device( int64_t hdl );

// service list
typedef struct  srv_info
{
	uint16_t  uuid;
	uint16_t  atthdl;
} srv_info_t;

int  test_srv_list( int64_t hdl, srv_info_t ** ppinfo );



typedef struct  _tag_chara_info
{
	uint16_t  srvHdl;
	uint16_t  uuid;
	uint16_t  declHdl;
	uint16_t  valueHdl;
	bool  IsBroadcastable;
	bool  IsReadable;
	bool  IsWritable;
  	bool  IsWritableWithoutResponse;
  	bool  IsSignedWritable;
  	bool  IsNotifiable;
  	bool  IsIndicatable;
  	bool  HasExtendedProperties;
} chara_info_t;

int  test_chara_list( int64_t hdl, srv_info_t * psrv, chara_info_t ** ppinfo );
int  test_chara_read( int64_t hdl, chara_info_t * pchara, uint8_t ** ppdat );
int  test_chara_read2( int64_t hdl, uint16_t srvuuid, uint16_t srvHdl, uint16_t charaHdl );
*/
import "C"

type BleThrd struct {
	tui io.StringWriter
	tp  chan string
}

func (thrd *BleThrd) WriteString(s string) (n int, err error) {
	thrd.tp <- s
	return len(s), nil
}

var devs map[uint32]string = make(map[uint32]string)

func (thrd *BleThrd) handleListDev(args []string) {
	var iret C.int
	var ctx C.intptr_t
	var ptr *C.char
	var idx uint32

	for k, _ := range devs {
		delete(devs, k)
	}

	iret = C.test_devs_init(&ctx)
	if iret != 0 {
		thrd.tui.WriteString(fmt.Sprintf("devs init, %v", iret))
		return
	}

	for idx = 0; ; idx += 1 {
		iret = C.test_devs_next(ctx, &ptr)
		if iret != 0 {
			break
		}

		/**/
		devs[idx] = C.GoString(ptr)
		C.free(unsafe.Pointer(ptr))

		/**/
		thrd.tui.WriteString(fmt.Sprintf("[%v]: %s", idx, devs[idx]))

	}

	/**/
	C.test_devs_fini(ctx)
	return
}

func (thrd *BleThrd) handleVersion(args []string) {

	thrd.tui.WriteString(fmt.Sprintf("args: %v", len(args)))
	thrd.tui.WriteString("version:1.1.1.1")
	return
}

type BleDevice struct {
	hdl C.int64_t
}

type BleService struct {
	pdev   *BleDevice
	uuid   uint16
	AttHdl uint16
}

type BleCharacteristic struct {
	psrv     *BleService
	uuid     uint16
	DeclHdl  uint16
	ValueHdl uint16

	IsBroadcastable           bool
	IsReadable                bool
	IsWritable                bool
	IsWritableWithoutResponse bool
	IsSignedWritable          bool
	IsNotifiable              bool
	IsIndicatable             bool
	HasExtendedProperties     bool
}

type BleDescriptor struct {
	pchar      *BleCharacteristic
	uuid       uint16
	descAttHdl uint16
	descType   int /* emum, BTH_LE_GATT_DESCRIPTOR_TYPE  */
}

func OpenDevice(fp string) (*BleDevice, error) {
	var dev BleDevice

	/**/
	tpath := C.CString(fp)
	defer C.free(unsafe.Pointer(tpath))
	dev.hdl = C.test_open_device(tpath)

	/**/
	if dev.hdl < 0 {
		return nil, fmt.Errorf("open device, fail")
	} else {
		return &dev, nil
	}

}

func (dev *BleDevice) CloseDevice() {
	/**/
	C.test_close_device(dev.hdl)
	return
}

func (dev *BleDevice) GetSrvList() ([]BleService, error) {
	var pinfo *C.srv_info_t

	iret := int(C.test_srv_list(C.int64_t(dev.hdl), &pinfo))
	if iret <= 0 {
		return nil, errors.New(fmt.Sprintf("srv list, fail=%d", iret))
	}

	defer C.free(unsafe.Pointer(pinfo))

	/**/
	tslic := (*[1 << 30]C.srv_info_t)(unsafe.Pointer(pinfo))[:iret:iret]
	srvs := make([]BleService, iret)

	for i := 0; i < iret; i++ {
		srvs[i].pdev = dev
		srvs[i].uuid = uint16(tslic[i].uuid)
		srvs[i].AttHdl = uint16(tslic[i].atthdl)
	}

	return srvs, nil
}

func (dev *BleDevice) FindSrvByHandle(attHdl uint16) (*BleService, error) {
	var pinfo *C.srv_info_t
	var srv BleService

	iret := int(C.test_srv_list(C.int64_t(dev.hdl), &pinfo))
	if iret <= 0 {
		return nil, fmt.Errorf("srv list, fail=%d", iret)
	}

	defer C.free(unsafe.Pointer(pinfo))

	/**/
	tslic := (*[1 << 30]C.srv_info_t)(unsafe.Pointer(pinfo))[:iret:iret]

	for i := 0; i < iret; i++ {
		if attHdl == uint16(tslic[i].atthdl) {
			srv.pdev = dev
			srv.uuid = uint16(tslic[i].uuid)
			srv.AttHdl = uint16(tslic[i].atthdl)
			return &srv, nil
		}
	}

	return nil, fmt.Errorf("not find the service, 0x%x", attHdl)
}

func (srv *BleService) GetCharaList() ([]BleCharacteristic, error) {
	var pinfo *C.chara_info_t
	var tsrv C.srv_info_t

	/**/
	tsrv.uuid = C.uint16_t(srv.uuid)
	tsrv.atthdl = C.uint16_t(srv.AttHdl)

	/**/
	iret := int(C.test_chara_list(srv.pdev.hdl, &tsrv, &pinfo))
	if iret <= 0 {
		return nil, fmt.Errorf("chara list, fail=%d", iret)
	}

	defer C.free(unsafe.Pointer(pinfo))

	/**/
	tslic := (*[1 << 30]C.chara_info_t)(unsafe.Pointer(pinfo))[:iret:iret]
	charas := make([]BleCharacteristic, iret)
	for i := 0; i < iret; i++ {
		charas[i].psrv = srv
		charas[i].uuid = uint16(tslic[i].uuid)
		charas[i].DeclHdl = uint16(tslic[i].declHdl)
		charas[i].ValueHdl = uint16(tslic[i].valueHdl)

		charas[i].IsBroadcastable = bool(tslic[i].IsBroadcastable)
	}

	/**/
	return charas, nil

}

func (srv *BleService) FindCharaByHandle(attHdl uint16) (*BleCharacteristic, error) {
	var pinfo *C.chara_info_t
	var tsrv C.srv_info_t
	var chara BleCharacteristic
	/**/
	tsrv.uuid = C.uint16_t(srv.uuid)
	tsrv.atthdl = C.uint16_t(srv.AttHdl)

	/**/
	iret := int(C.test_chara_list(srv.pdev.hdl, &tsrv, &pinfo))
	if iret <= 0 {
		return nil, fmt.Errorf("chara list, fail=%d", iret)
	}

	defer C.free(unsafe.Pointer(pinfo))

	/**/
	tslic := (*[1 << 30]C.chara_info_t)(unsafe.Pointer(pinfo))[:iret:iret]

	for i := 0; i < iret; i++ {

		if attHdl == uint16(tslic[i].declHdl) {

			chara.psrv = srv
			chara.uuid = uint16(tslic[i].uuid)
			chara.DeclHdl = uint16(tslic[i].declHdl)
			chara.ValueHdl = uint16(tslic[i].valueHdl)

			chara.IsBroadcastable = bool(tslic[i].IsBroadcastable)
			chara.IsReadable = bool(tslic[i].IsReadable)
			chara.IsWritable = bool(tslic[i].IsWritable)
			chara.IsWritableWithoutResponse = bool(tslic[i].IsWritableWithoutResponse)
			chara.IsSignedWritable = bool(tslic[i].IsSignedWritable)
			chara.IsNotifiable = bool(tslic[i].IsNotifiable)
			chara.IsIndicatable = bool(tslic[i].IsIndicatable)
			chara.HasExtendedProperties = bool(tslic[i].HasExtendedProperties)

			/**/
			return &chara, nil
		}
	}

	/**/
	return nil, fmt.Errorf("not find the chara, 0x%x", attHdl)

}

func (cara *BleCharacteristic) GetDescList() *BleDescriptor {
	return nil
}

func (cara *BleCharacteristic) FindDescByHandle(attHdl uint16) *BleDescriptor {
	return nil
}

func (cara *BleCharacteristic) ReadValue() ([]byte, error) {

	var pdat *C.uint8_t
	var tchara C.chara_info_t

	/**/
	tchara.srvHdl = C.uint16_t(cara.psrv.AttHdl)
	tchara.uuid = C.uint16_t(cara.uuid)
	tchara.declHdl = C.uint16_t(cara.DeclHdl)
	tchara.valueHdl = C.uint16_t(cara.ValueHdl)

	tchara.IsBroadcastable = C.bool(cara.IsBroadcastable)
	tchara.IsReadable = C.bool(cara.IsReadable)
	tchara.IsWritable = C.bool(cara.IsWritable)
	tchara.IsWritableWithoutResponse = C.bool(cara.IsWritableWithoutResponse)
	tchara.IsSignedWritable = C.bool(cara.IsSignedWritable)
	tchara.IsNotifiable = C.bool(cara.IsNotifiable)
	tchara.IsIndicatable = C.bool(cara.IsIndicatable)
	tchara.HasExtendedProperties = C.bool(cara.HasExtendedProperties)

	/**/
	iret := C.test_chara_read(cara.psrv.pdev.hdl, &tchara, &pdat)
	if iret <= 0 {
		return nil, fmt.Errorf("chara read, fail=%d", int(iret))
	}

	/**/
	defer C.free(unsafe.Pointer(pdat))
	return C.GoBytes(unsafe.Pointer(pdat), iret), nil
}

var pdev *BleDevice = nil

func (thrd *BleThrd) handleOpenDev(args []string) {
	if len(args) <= 0 {
		thrd.tui.WriteString("args, need index, <int>")
		return
	}

	tmp, err := strconv.ParseUint(args[0], 0, 32)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("parseInt, %v", err))
		return
	}

	if tmp < 0 {
		thrd.tui.WriteString(fmt.Sprintf("index range err, %v", tmp))
		return
	}

	/**/
	pstr := devs[uint32(tmp)]
	if pstr == "" {
		thrd.tui.WriteString(fmt.Sprintf("index range err, %v", tmp))
		return
	}

	/**/
	thrd.tui.WriteString("path:" + pstr)
	pdev, err = OpenDevice(pstr)

	/**/
	if err != nil {
		thrd.tui.WriteString("open fail," + err.Error())
	} else {
		thrd.tui.WriteString("open succ..")
	}

	return
}

func (thrd *BleThrd) handleCloseDev(args []string) {
	if pdev == nil {
		thrd.tui.WriteString("handle invalid..")
		return
	}

	/**/
	pdev.CloseDevice()
	thrd.tui.WriteString("close succ..")
	pdev = nil
	return
}

func (thrd *BleThrd) handleSrvList(args []string) {

	if pdev == nil {
		thrd.tui.WriteString("device handle invalid")
		return
	}

	srvs, err := pdev.GetSrvList()
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("srv list fail, %v", err))
	}

	for i, ss := range srvs {
		thrd.tui.WriteString(fmt.Sprintf("[%d]:  uuid: %x, atthdl: %x", i, ss.uuid, ss.AttHdl))
	}

	return
}

func (thrd *BleThrd) handleCharaList(args []string) {

	var srvAttHdl uint16

	if len(args) <= 0 {
		thrd.tui.WriteString("args, need index, <int>")
		return
	}

	tmp, err := strconv.ParseUint(args[0], 0, 32)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("parseInt, %v", err))
		return
	}

	if pdev == nil {
		thrd.tui.WriteString("device handle invalid")
		return
	}

	srvAttHdl = uint16(tmp)
	srv, err := pdev.FindSrvByHandle(srvAttHdl)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("find srv by handle, fail, %v", err))
		return
	}

	/**/
	charas, err := srv.GetCharaList()
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("get chara list, fail, %v", err))
		return
	}

	for i, chara := range charas {
		thrd.tui.WriteString(fmt.Sprintf("[%d], uuid: %x, declHdl:%x", i, chara.uuid, chara.DeclHdl))
	}

	return
}

func (thrd *BleThrd) handleCharaRead(args []string) {
	var tmp uint64
	var srvAttHdl uint16
	var charaAttHdl uint16

	if len(args) < 2 {
		thrd.tui.WriteString("args, need index, <int>")
		return
	}

	tmp, err := strconv.ParseUint(args[0], 0, 32)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("parseInt, args[0], %v", err))
		return
	}

	srvAttHdl = uint16(tmp)

	tmp, err = strconv.ParseUint(args[1], 0, 32)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("parseInt, args[1], %v", err))
		return
	}

	charaAttHdl = uint16(tmp)

	if pdev == nil {
		thrd.tui.WriteString("device handle invalid")
		return
	}

	/**/
	srv, err := pdev.FindSrvByHandle(srvAttHdl)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("find srv: %v", err))
		return
	}

	chara, err := srv.FindCharaByHandle(charaAttHdl)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("find chara: %v", err))
		return
	}

	/**/
	fmt.Printf("serv: %x, decl: %x, value:%x \n", srv.AttHdl, chara.DeclHdl, chara.ValueHdl)

	/**/
	dat, err := chara.ReadValue()
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("read value: %v", err))
		return
	}

	/**/
	thrd.tui.WriteString(hex.Dump(dat))
	return
}

func (thrd *BleThrd) handleCharaRead2(args []string) {
	var tmp uint64
	var srvAttHdl uint16
	var charaAttHdl uint16

	if len(args) < 2 {
		thrd.tui.WriteString("args, need index, <int>")
		return
	}

	tmp, err := strconv.ParseUint(args[0], 0, 32)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("parseInt, args[0], %v", err))
		return
	}

	srvAttHdl = uint16(tmp)

	tmp, err = strconv.ParseUint(args[1], 0, 32)
	if err != nil {
		thrd.tui.WriteString(fmt.Sprintf("parseInt, args[1], %v", err))
		return
	}

	charaAttHdl = uint16(tmp)

	if pdev == nil {
		thrd.tui.WriteString("device handle invalid")
		return
	}

	iret := int(C.test_chara_read2(pdev.hdl, 0x1800, C.uint16_t(srvAttHdl), C.uint16_t(charaAttHdl)))
	thrd.tui.WriteString(fmt.Sprintf("read, iret, %d", iret))
	return
}

func tloopRoute(thrd *BleThrd) {
	for {
		istr := <-thrd.tp
		args := strings.Fields(istr)

		if len(args) <= 0 {
			/* noting */
			continue
		}

		switch args[0] {
		case "ver":
			thrd.handleVersion(args[1:])

		case "list":
			thrd.handleListDev(args[1:])

		case "open":
			thrd.handleOpenDev(args[1:])

		case "close":
			thrd.handleCloseDev(args[1:])

		case "srv_list":
			thrd.handleSrvList(args[1:])

		case "chara_list":
			thrd.handleCharaList(args[1:])

		case "chara_read":
			thrd.handleCharaRead2(args[1:])

		case "desc_list":
		case "desc_read":
		default:
			thrd.tui.WriteString("uknow:" + args[0])
		}

	}
}

func NewThread(tui io.StringWriter) io.StringWriter {
	var thrd BleThrd

	/**/
	thrd.tp = make(chan string, 4)
	thrd.tui = tui

	/**/
	go tloopRoute(&thrd)
	return &thrd
}
